#!/usr/bin/env python
import random
import itertools

import wx

from pandac.PandaModules import Quat, OdePlaneGeom, Mat4

import pvis
import ode3d.wxapp

class Top(ode3d.GDMBody):
    def __init__(self, World, Diameter, Height):
        # initialize the parent class
        ode3d.GDMBody.__init__(self, World)
    
        shaft = ode3d.GDMElement().DefineCylinder(2000, Diameter / 100.0, Height)
        shaft.SetCollisionBitfields(2,1) # Category = 1, collide with 0
        self.AddElement('shaft', shaft)
        print 'shaft', shaft._mass.getMagnitude()
        
        disk = ode3d.GDMElement().DefineCylinder(2000, Diameter, Height / 5.0)
        disk.SetCollisionBitfields(1,1) # Category = 0, collide with 0
        disk.setOffsetPosition((0,0, 0.3 * Height)) # Must be called after AddElement
        self.AddElement('disk', disk)
        print 'disk', disk._mass.getMagnitude()

        self.AddElement('cg', ode3d.DisplayElement(pvis.sphere(radius=0.05)))

class MainWindow(wx.Frame):
    def __init__(self, *args, **kwargs):
        kwargs.setdefault('parent', None)
        size = kwargs.get('size', (640, 480))
        
        # Make size bigger so our client area will be equal to size
        #kwargs['size'] = (size[0] + 250, size[1] + 400)
        
        kwargs['style'] = kwargs.setdefault('style', wx.DEFAULT_FRAME_STYLE) | wx.NO_FULL_REPAINT_ON_RESIZE
        kwargs.setdefault('title', 'Ode3d OpenGL Display Window')

        # Call the parent class with our (modified) arguments        
        wx.Frame.__init__(self, *args, **kwargs)

        boxH = wx.BoxSizer(wx.HORIZONTAL)
        boxH.Add(pvis.pwx.PandaPanel(self, wx.ID_ANY), 2, wx.EXPAND | wx.ALL, 5)
        
        self.statusAreaR = ode3d.wxapp.StatusArea(6, 2, 10, self, wx.ID_ANY)
        boxH.Add(self.statusAreaR.panel, 1, wx.EXPAND | wx.ALL, 5)
        
        boxV = wx.BoxSizer(wx.VERTICAL)
        boxV.Add(boxH, 5, wx.EXPAND | wx.ALL, 5)
        
        self.statusAreaB = ode3d.wxapp.StatusArea(1, 3, 10, self, wx.ID_ANY)
        boxV.Add(self.statusAreaB.panel, 1, wx.EXPAND | wx.ALL, 5)

        self.SetAutoLayout(True)
        self.SetSizer(boxV)
        self.Layout()
        boxV.Fit(self)

        # StatusBar
        self.CreateStatusBar()

        # Filemenu
        filemenu = wx.Menu()

        # Filemenu - About
        menuitem = filemenu.Append(-1, "&About", "Information about this program")
        self.Bind(wx.EVT_MENU, self.OnAbout, menuitem) # here comes the event-handler
        # Filemenu - Separator
        filemenu.AppendSeparator()

        # Filemenu - Exit
        menuitem = filemenu.Append(-1, "E&xit", "Terminate the program")
        self.Bind(wx.EVT_MENU, self.OnExit, menuitem) # here comes the event-handler

        # Menubar
        menubar = wx.MenuBar()
        menubar.Append(filemenu,"&File")
        self.SetMenuBar(menubar)

        # Show -- already done by the panda panel
        # self.Show(True)

    def OnAbout(self,event):
        message = "Ode3d With wxPython"
        caption = "About Ode3d Application"
        wx.MessageBox(message, caption, wx.OK)

    def OnExit(self,event):
        self.Close(True)  # Close the frame.

    def SetUpdateLoop(self, UpdateLoopFunc=lambda:None):
        self.scene.SetUpdateLoop(UpdateLoopFunc)

class MyApp(ode3d.wxapp.wxOdeApp):
    def __init__(self):
        fps = 30 # 30 frames per second
        dt = 1/60.0 # the target rate for our sim time step
        gravity = (0,0,-9.81)
        
        # Now we can initialize the pvis.base class
        ode3d.wxapp.wxOdeApp.__init__(self, dt, fps, Gravity=gravity)

        # Setup our application frame
        self.frame = MainWindow(size=(640,480), title='SpinningTop')
        self.simState.statusAreaB = self.frame.statusAreaB  
        self.simState.statusAreaR = self.frame.statusAreaR  

        # Setup a random error generator
        rand = random.Random()
        error = lambda: rand.gauss(0.0, 0.01)  

        # Add arrows for the world axes and a sphere for the origin
        self.az = pvis.arrow(length=0.5, color=(0.2,0.2,0.5))
        self.origin = pvis.sphere(radius=self.az.shaftwidth/2)
        
        # Create a plane geom for the floor that the top will spin on
        self.plane = OdePlaneGeom(self.world.space, 0, 0, 1, 0)

        # Create a display object for the floor
        self.floor = pvis.box(size=(10,10,.0001), color=(.2,.2,.2))
        
        # Create our top
        top = Top(self.world, 1, 1)
        self.AddObj('top', top)

        # Position the top above the plane so it will drop onto it when the sim starts
        top.setPosition((0,0,5))
        
        # Apply a random initial orientation error to our top
        q = Quat(top.getQuaternion())
        qr = Quat(1, error(), error(), error())
        qr.normalize()
        q *= qr
        top.setQuaternion(q)

        # Put an initial spin on our top
        top.setAngularVel((0, 0, 10))

        # Create an initial camera position
        pvis.base.disableMouse()
        pvis.base.camera.setPos(4, 4, 1)
        pvis.base.camera.lookAt(0, 0, 0) #@UndefinedVariable -- lookAt(), more Panda3d magic?

        # Turn mouse control back on
        mat=Mat4(pvis.base.camera.getMat()) #@UndefinedVariable -- getMat(), more Panda3d magic?
        mat.invertInPlace()
        pvis.base.mouseInterfaceNode.setMat(mat)
        pvis.base.enableMouse()
        
        # Replaces the default panda based status text provided by pvis 
        self._statusDisplay = self.simState.statusAreaB.textBoxes[0]

if __name__ == '__main__':
    app = MyApp()
    #app.Run(Static=True)
    app.Run(0)
